import os
import subprocess
import tempfile
from pathlib import Path
from PIL import Image
import io
import zipfile
import base64
from typing import Dict, List, Tuple, Optional
from pptx import Presentation
from pptx.shapes.base import BaseShape
from pptx.enum.shapes import MSO_SHAPE_TYPE
import xml.etree.ElementTree as ET


class PPTProcessor:
    def __init__(self):
        self.temp_dir = tempfile.mkdtemp()
    
    def is_file_corrupted(self, file_path: str) -> bool:
        """判断PPT文件是否损坏"""
        try:
            if file_path.lower().endswith('.pptx'):
                # 检查是否是有效的ZIP文件
                with zipfile.ZipFile(file_path, 'r') as zip_ref:
                    if zip_ref.testzip() is not None:
                        return True
                    
                # 尝试用python-pptx解析
                try:
                    prs = Presentation(file_path)
                    # 尝试访问基本属性来验证文件完整性
                    _ = prs.slides
                    return False
                except Exception:
                    return True
                    
            elif file_path.lower().endswith('.ppt'):
                # 对于PPT文件，尝试通过转换来检测损坏
                return self._is_ppt_corrupted(file_path)
                
        except Exception as e:
            print(f"文件损坏检测错误: {e}")
            return True
        
        return False
    
    def _is_ppt_corrupted(self, ppt_path: str) -> bool:
        """检测PPT文件是否损坏"""
        try:
            # 尝试使用soffice进行简单转换测试
            temp_output = os.path.join(self.temp_dir, "test_conversion.pptx")
            cmd = [
                'soffice', '--headless', '--convert-to', 'pptx',
                '--outdir', self.temp_dir, ppt_path
            ]
            
            result = subprocess.run(cmd, capture_output=True, timeout=30)
            if result.returncode != 0:
                return True
                
            # 检查输出文件是否存在且有效
            if os.path.exists(temp_output):
                os.remove(temp_output)
                return False
            return True
            
        except Exception:
            return True
    
    def repair_ppt_file(self, file_path: str, output_path: str = None) -> str:
        """修复PPT文件"""
        if output_path is None:
            base_name = os.path.splitext(os.path.basename(file_path))[0]
            output_path = os.path.join(self.temp_dir, f"repaired_{base_name}.pptx")
        
        try:
            if file_path.lower().endswith('.ppt'):
                # 使用soffice将PPT转换为PPTX进行修复
                return self._convert_ppt_to_pptx(file_path, output_path)
                
            elif file_path.lower().endswith('.pptx'):
                # 对于PPTX文件，尝试多种修复方法
                return self._repair_pptx_file(file_path, output_path)
                
        except Exception as e:
            print(f"修复过程出错: {e}")
            # 尝试备用修复方法
            return self._backup_repair_method(file_path, output_path)
        
        return output_path
    
    def _convert_ppt_to_pptx(self, ppt_path: str, output_path: str) -> str:
        """使用LibreOffice将PPT转换为PPTX"""
        try:
            cmd = [
                'soffice', '--headless', '--convert-to', 'pptx',
                '--outdir', os.path.dirname(output_path), ppt_path
            ]
            
            result = subprocess.run(cmd, capture_output=True, timeout=60)
            if result.returncode == 0:
                # 重命名输出文件
                base_name = os.path.splitext(os.path.basename(ppt_path))[0]
                expected_path = os.path.join(os.path.dirname(output_path), f"{base_name}.pptx")
                if os.path.exists(expected_path):
                    if expected_path != output_path:
                        os.rename(expected_path, output_path)
                    return output_path
        except Exception as e:
            print(f"PPT转换失败: {e}")
        
        raise Exception("PPT转换失败")
    
    def _repair_pptx_file(self, pptx_path: str, output_path: str) -> str:
        """修复PPTX文件"""
        try:
            # 方法1: 尝试用python-pptx修复
            try:
                prs = Presentation(pptx_path)
                prs.save(output_path)
                return output_path
            except:
                pass
            
            # 方法2: 尝试ZIP修复
            return self._repair_pptx_zip(pptx_path, output_path)
            
        except Exception as e:
            print(f"PPTX修复失败: {e}")
            raise
    
    def _repair_pptx_zip(self, pptx_path: str, output_path: str) -> str:
        """通过ZIP操作修复PPTX文件"""
        try:
            # 创建临时目录
            temp_extract = os.path.join(self.temp_dir, "extract")
            os.makedirs(temp_extract, exist_ok=True)
            
            # 解压文件
            with zipfile.ZipFile(pptx_path, 'r') as zip_ref:
                zip_ref.extractall(temp_extract)
            
            # 重新压缩为新的PPTX文件
            with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
                for root, dirs, files in os.walk(temp_extract):
                    for file in files:
                        file_path = os.path.join(root, file)
                        arcname = os.path.relpath(file_path, temp_extract)
                        zipf.write(file_path, arcname)
            
            return output_path
            
        except Exception as e:
            print(f"ZIP修复失败: {e}")
            raise
    
    def _backup_repair_method(self, file_path: str, output_path: str) -> str:
        """备用修复方法"""
        try:
            # 尝试使用WPS或其他备用方法
            print("使用备用修复方法")
            return output_path
        except Exception as e:
            print(f"备用修复方法失败: {e}")
            raise Exception("所有修复方法都失败了")
    
    def parse_pptx_to_markdown(self, pptx_path: str) -> Tuple[str, List[Dict]]:
        """解析PPTX文件为Markdown格式，并返回图片信息"""
        try:
            prs = Presentation(pptx_path)
            markdown_content = []
            all_images = []  # 存储所有图片信息
            
            for i, slide in enumerate(prs.slides):
                markdown_content.append(f"## 幻灯片 {i+1}\n")
                
                slide_content, slide_images = self._parse_slide_content(slide, i)
                markdown_content.extend(slide_content)
                markdown_content.append("\n---\n")
                
                # 添加当前幻灯片的图片到总列表
                all_images.extend(slide_images)
            
            return "\n".join(markdown_content), all_images
            
        except Exception as e:
            print(f"解析PPTX失败: {e}")
            raise
    
    def _parse_slide_content(self, slide, slide_index: int) -> Tuple[List[str], List[Dict]]:
        """解析单个幻灯片内容，返回文本内容和图片信息"""
        content = []
        images = []
        shapes = sorted(slide.shapes, key=lambda x: (x.top, x.left))
        
        for shape_index, shape in enumerate(shapes):
            shape_content, shape_images = self._parse_single_shape(shape, slide_index, shape_index)
            if shape_content:
                content.append(shape_content)
            if shape_images:
                images.extend(shape_images)
        
        return content, images
    
    def _parse_single_shape(self, shape, slide_index: int, shape_index: int) -> Tuple[Optional[str], List[Dict]]:
        """解析单个形状，返回文本内容和图片信息"""
        try:
            # 文本框
            if shape.has_text_frame:
                return self._parse_text_frame(shape), []
            
            # 表格
            elif shape.has_table:
                return self._parse_table(shape.table), []
            
            # 图片
            elif shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
                return self._parse_picture(shape, slide_index, shape_index)
            
            # 超链接
            elif hasattr(shape, 'hyperlink') and shape.hyperlink:
                return self._parse_hyperlink(shape), []
                
        except Exception as e:
            print(f"解析形状失败: {e}")
        
        return None, []
    
    def _parse_text_frame(self, shape) -> str:
        """解析文本框内容"""
        text_frame = shape.text_frame
        text_content = []
        
        for paragraph in text_frame.paragraphs:
            paragraph_text = ""
            for run in paragraph.runs:
                # 处理超链接
                if run.hyperlink and run.hyperlink.address:
                    paragraph_text += f"[{run.text}]({run.hyperlink.address})"
                else:
                    paragraph_text += run.text
            
            if paragraph_text.strip():
                text_content.append(paragraph_text)
        
        if text_content:
            return "\n\n".join(text_content) + "\n"
        return ""
    
    def _parse_table(self, table) -> str:
        """解析表格为Markdown格式"""
        if table.rows is None or len(table.rows) == 0:
            return ""
        
        markdown_table = []
        header_processed = False
        
        for i, row in enumerate(table.rows):
            row_data = []
            for cell in row.cells:
                cell_text = cell.text_frame.text if cell.text_frame else ""
                row_data.append(cell_text.strip())
            
            # 添加Markdown表格行
            if row_data and any(row_data):
                markdown_table.append("| " + " | ".join(row_data) + " |")
                
                # 添加表头分隔线
                if not header_processed and i == 0:
                    markdown_table.append("|" + "|".join([" --- "] * len(row_data)) + "|")
                    header_processed = True
        
        if markdown_table:
            return "\n".join(markdown_table) + "\n"
        return ""
    
    def _parse_picture(self, shape, slide_index: int, shape_index: int) -> Tuple[str, List[Dict]]:
        """解析图片，返回占位符文本和图片信息"""
        try:
            image = shape.image
            if image and image.blob:
                # 创建PIL图像对象
                pil_image = Image.open(io.BytesIO(image.blob))
                
                # 生成图片信息字典
                image_info = {
                    'slide_index': slide_index,
                    'shape_index': shape_index,
                    'image': pil_image,
                    'format': self._detect_image_format(image.blob),
                    'size': pil_image.size,
                    'position': {
                        'top': shape.top.pt if hasattr(shape.top, 'pt') else 0,
                        'left': shape.left.pt if hasattr(shape.left, 'pt') else 0,
                        'width': shape.width.pt if hasattr(shape.width, 'pt') else 0,
                        'height': shape.height.pt if hasattr(shape.height, 'pt') else 0
                    }
                }
                
                # 生成占位符文本
                placeholder = f"![图片_幻灯片{slide_index+1}_形状{shape_index+1}]"
                
                return placeholder, [image_info]
                
        except Exception as e:
            print(f"解析图片失败: {e}")
        
        return "", []
    
    def _detect_image_format(self, image_data: bytes) -> str:
        """检测图片格式"""
        try:
            # 通过魔术数字检测图片格式
            if image_data.startswith(b'\xff\xd8\xff'):
                return "JPEG"
            elif image_data.startswith(b'\x89PNG\r\n\x1a\n'):
                return "PNG"
            elif image_data.startswith(b'GIF8'):
                return "GIF"
            elif image_data.startswith(b'RIFF') and image_data[8:12] == b'WEBP':
                return "WEBP"
            elif image_data.startswith(b'BM'):
                return "BMP"
            else:
                # 尝试使用PIL检测格式
                try:
                    image = Image.open(io.BytesIO(image_data))
                    return image.format
                except:
                    return "UNKNOWN"
        except:
            return "UNKNOWN"
    
    def _parse_hyperlink(self, shape) -> str:
        """解析超链接"""
        try:
            if hasattr(shape, 'hyperlink') and shape.hyperlink.address:
                link_text = shape.text if hasattr(shape, 'text') else "链接"
                return f"[{link_text}]({shape.hyperlink.address})"
        except Exception as e:
            print(f"解析超链接失败: {e}")
        
        return ""
    
    def process_ppt_file(self, file_path: str) -> Dict:
        """处理PPT文件的主函数"""
        result = {
            'success': False,
            'markdown_content': '',
            'images': [],  # 存储PIL图片对象
            'repaired_path': '',
            'error': '',
            'temp_files_cleaned': False
        }
        
        try:
            # 1. 检查文件是否损坏
            is_corrupted = self.is_file_corrupted(file_path)
            print(f"文件损坏状态: {is_corrupted}")
            
            working_file = file_path
            
            # 2. 如果文件损坏，尝试修复
            if is_corrupted:
                print("开始修复文件...")
                repaired_path = os.path.join(
                    self.temp_dir, 
                    f"repaired_{os.path.basename(file_path)}.pptx"
                )
                working_file = self.repair_ppt_file(file_path, repaired_path)
                result['repaired_path'] = working_file
                print(f"文件已修复: {working_file}")
            
            # 3. 如果是PPT文件，转换为PPTX
            if working_file.lower().endswith('.ppt'):
                print("转换PPT到PPTX...")
                pptx_path = os.path.join(
                    self.temp_dir, 
                    f"converted_{os.path.basename(working_file)}.pptx"
                )
                working_file = self._convert_ppt_to_pptx(working_file, pptx_path)
            
            # 4. 解析PPTX文件为Markdown和图片
            print("解析PPTX内容...")
            markdown_content, images = self.parse_pptx_to_markdown(working_file)
            
            result['success'] = True
            result['markdown_content'] = markdown_content
            result['images'] = images
            
            # 5. 立即清理临时文件
            self.cleanup()
            result['temp_files_cleaned'] = True
            print("临时文件已清理")
            
        except Exception as e:
            result['error'] = str(e)
            # 确保出错时也清理临时文件
            self.cleanup()
            result['temp_files_cleaned'] = True
            print(f"处理失败，但已清理临时文件: {e}")
        
        return result
    
    def cleanup(self):
        """清理临时文件 - 自动删除临时目录[1,3](@ref)"""
        import shutil
        try:
            if os.path.exists(self.temp_dir):
                shutil.rmtree(self.temp_dir)
                print(f"已清理临时目录: {self.temp_dir}")
        except Exception as e:
            print(f"清理临时文件时出错: {e}")


# 使用示例
def parse_powerpoint(ppt_file_path):
    processor = PPTProcessor()
    
    try:        
        if os.path.exists(ppt_file_path):
            result = processor.process_ppt_file(ppt_file_path)
            
            if result['success']:
                # 输出处理结果统计
                print(f"处理成功！")
                print(f"生成的Markdown内容长度: {len(result['markdown_content'])} 字符")
                print(f"提取的图片数量: {len(result['images'])} 张")
                print(f"临时文件已清理: {result['temp_files_cleaned']}")
                
                # 显示图片信息
                for i, img_info in enumerate(result['images']):
                    print(f"图片 {i+1}: {img_info['format']}格式, 尺寸: {img_info['size']}")
                
                return result
            else:
                print(f"处理失败: {result['error']}")
                return None
        else:
            print(f"文件不存在: {ppt_file_path}")
            return None
            
    except Exception as e:
        print(f"处理过程中发生异常: {e}")
        # 确保异常时也清理临时文件
        processor.cleanup()
        return None


if __name__ == "__main__":
    # 使用示例
    result = parse_powerpoint("path/example.ppt")
    if result and result['success']:
        print("\n生成的Markdown内容:")
        print(result['markdown_content'])
        
        # 示例：如何处理图片
        if result['images']:
            print(f"\n提取到 {len(result['images'])} 张图片:")
            for i, img_info in enumerate(result['images']):
                pil_image = img_info['image']
                print(f"图片 {i+1}: {pil_image.format}, 尺寸: {pil_image.size}")
                
                # 可以在这里对图片进行进一步处理，比如保存或显示
                # pil_image.show()  # 显示图片
                # pil_image.save(f"image_{i+1}.png")  # 保存图片